Fix duplicate conflict dialog when moving files across volumes#3772
Open
kristofszeles wants to merge 1 commit into
Open
Fix duplicate conflict dialog when moving files across volumes#3772kristofszeles wants to merge 1 commit into
kristofszeles wants to merge 1 commit into
Conversation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
When moving a file to a different volume (different filesystem) and a file with the same name already exists at the destination, the file-conflict dialog is shown twice instead of once. The new filename entered by the user in the first dialog is silently dropped; only the name re-typed in the second dialog actually takes effect.
The same operation works correctly when copying between volumes — only move is affected.
Reproduction steps
/home(your user partition) and an external USB drive mounted at/run/media/<user>/USB.foo.txtin your home directory and another file with the same namefoo.txton the USB drive.~/foo.txt(Ctrl+X) and paste it into the USB drive (Ctrl+V). Drag-and-drop between the two volumes triggers the bug as well.foo-renamed.txt), and click Rename.Expected: the file is moved as
foo-renamed.txtand the dialog closes.Actual: the conflict dialog reappears, still showing the original conflict against
foo.txt. Re-typing the samefoo-renamed.txtand clicking Rename the second time succeeds.The bug is not reproducible with copy (Ctrl+C / Paste) — only with move (Ctrl+X / Paste, or drag-and-drop between volumes).
Root cause
The bug lives in
move_file_prepare()inlibnemo-private/nemo-file-operations.c. The function exists to filter move operations into two buckets:rename(2)(same filesystem).MoveFileCopyFallbacklist and processed later bycopy_move_file().For each file it performs a trial call to
g_file_move()withG_FILE_COPY_NO_FALLBACK_FOR_MOVE. The behavior of GIO for that call is:OVERWRITEis not set, it returnsG_IO_ERROR_EXISTS.rename(2). On a cross-filesystem move,rename(2)fails withEXDEV, which GIO maps toG_IO_ERROR_NOT_SUPPORTED.So when moving
foo.txtto a volume that already containsfoo.txt, the sequence inmove_file_prepare()is:g_file_move()returnsG_IO_ERROR_EXISTS.foo-renamed.txt. The localdestGFile is replaced with one pointing atfoo-renamed.txt.goto retry.g_file_move()is called again. This time the existence check passes (foo-renamed.txtdoes not exist at the destination), andrename(2)is attempted. Because the source and destination are on different filesystems, it fails withG_IO_ERROR_NOT_SUPPORTED.WOULD_RECURSE / WOULD_MERGE / NOT_SUPPORTEDbranch is taken, which calls:MoveFileCopyFallbackonly storessrc,overwriteandposition— the renameddestis discarded.move_files()consumes the fallback and callscopy_move_file(src, job->destination, …).copy_move_file()recomputes the destination from scratch:foo.txt), not the renamed one.g_file_copy()returnsG_IO_ERROR_EXISTSagain, andcopy_move_file()shows the conflict dialog a second time.foo-renamed.txt. This time the rename happens insidecopy_move_file()'s own retry loop, which actually performs the copy, so the operation succeeds.The copy path is unaffected because
nemo_file_operations_copy()callscopy_files() → copy_move_file()directly. There is no preliminary trial that throws away the user's chosen filename.Fix
The bug is, at its core, a missing piece of state on the fallback record: when the user picks a new name in the prepare phase, that choice has no slot to live in until
copy_move_file()runs. The fix adds that slot and propagates the user's chosen destination through the fallback, socopy_move_file()reuses it instead of recomputing the original target.This keeps the existing UX intact — the conflict dialog still appears during the prepare phase (before
scan_sources()walks the source tree), so the user can cancel a large cross-volume move at the conflict prompt without paying the cost of the scan first.Changes in
libnemo-private/nemo-file-operations.cMoveFileCopyFallbackgains aGFile *precomputed_destfield:A custom destructor
move_file_copy_fallback_free()releases the reference and frees the struct:move_copy_file_callback_new()takes the precomputed destination andg_object_refs it (NULL is allowed and means "compute it normally"):copy_move_file()gains aGFile *precomputed_destparameter (forward declaration and definition both updated). When non-NULL, it is used directly instead of callingget_target_file_with_custom_name()/get_target_file():move_file_prepare()passes the (possibly user-renamed)destinto the fallback at the point where the cross-FS retry returnsNOT_SUPPORTED:move_files()forwardsfallback->precomputed_destintocopy_move_file(), completing the round-trip from the prepare phase to the transfer phase.The other call sites of
copy_move_file()—copy_files()and the recursive call insidecopy_move_directory()— passNULLfor the new parameter and remain behaviorally unchanged.move_job()frees the fallback list withmove_file_copy_fallback_freeinstead of plaing_free, so the new GFile reference is released along with the rest of the fallback record.Upstream
I have filed a pull request for the exact same issue against GNOME's official Nautilus repository with a slightly different solution: https://gitlab.gnome.org/GNOME/nautilus/-/merge_requests/2024